package com.sxpi.user.service.useramount;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sxpi.common.result.R;
import com.sxpi.user.dto.UserAmountDto;
import com.sxpi.user.mapper.UserAmountRecordMapper;
import com.sxpi.user.mapper.UserMapper;
import com.sxpi.user.pojo.User;
import com.sxpi.user.pojo.UserAmountRecord;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;

/**
 * @Author: 董伟豪
 * @Date: 2022/11/8 21:14
 */
@Service
@Slf4j
public class IUserAmountRecordZookeeperServiceImpl extends ServiceImpl<UserAmountRecordMapper, UserAmountRecord>
        implements IUserAmountRecordZookeeperService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private CuratorFramework client;

    /**
     * 提现方法---redis
     * 锁来保证数据的原子性  事务保证数据的完整性
     * @param userAmountDto
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    // 基于Redis的分布式锁这里加上事务就会出现问题
    // @GlobalTransactional
    public R takeMoneyZookeeperLock(UserAmountDto userAmountDto) {
        // 提现的用户
        Long userId = userAmountDto.getUserId();
        // 提现的金额
        Double money = userAmountDto.getMoney();

        // 所有并发线程产生的序号存放的目录位置（用户自己的隔离，自己的线程排他）
        // A ---并发的线程---/useramount/zklock/1-clock 0001 0002
        // B ---并发的线程---/useramount/zklock/2-clock 0001 0002 0003
        // C ---并发的线程---/useramount/zklock/3-clock 0001
        String lockPath = "/useramount/zklock/" + userId + "-lock";
        // 抢购和秒杀
        // String lockPath = "/useramount/zklock/1";
        // A， B ---并发的线程---/useramount/zklock/1 0001 0002 0003 0004 0005
        // 抢购和秒杀
        // String lockPath = "/useramount/zklock/";
        // A， B ---并发的线程---/useramount/zklock 0001 0002 0003 0004 0005

        InterProcessMutex interProcessMutexLock = new InterProcessMutex(client, lockPath);
        try {
            // 可以说，这个isLock永远都是true，因为这个相同节点会进行排队
            Boolean isLock = interProcessMutexLock.acquire(20L, TimeUnit.SECONDS);
            if(isLock) {
                // 无锁
                User user = userMapper.selectById(userId);
                // 判断用户提现的余额是否充足，如果充足就提现
                if (user != null && user.getAmount().doubleValue() - money > 0) {
                    // 如果余额充足，就开始扣减余额
                    User updateUser = new User();
                    updateUser.setId(userId);
                    updateUser.setAmount(new BigDecimal(user.getAmount().doubleValue() - money));
                    // 这行代码只是为了同步一些余额，避免再次查询数据库
                    user.setAmount(updateUser.getAmount());
                    // 更新用户余额
                    userMapper.updateById(updateUser);

                    // 同时插入提现记录信息保存到用户提现表中
                    UserAmountRecord userAmountRecord = new UserAmountRecord();
                    userAmountRecord.setUserId(userId);
                    userAmountRecord.setUsername(user.getUsername());
                    userAmountRecord.setAvatar(user.getAvatar());
                    userAmountRecord.setMoney(new BigDecimal(money));
                    // 保存用户提现记录
                    this.saveOrUpdate(userAmountRecord);

                    // 输出日志
                    log.info("当前提现的金额是：{}，用户的余额是：{}", money, user.getAmount());
                } else {
                    // 否则就返回账户余额不足
                    return R.error("账户不存在，或者余额不足!!!");
                }
                return R.ok();
            }else{
                // 经过测试以后，我的zookeeper根本到不了这里，因为是在排队（可重入锁）
                return R.error("别着急，等慢一点~");
            }
        } catch (Exception e) {
            return R.error("执行失败....");
        } finally {
            // 无论发送任何情况。都需要将锁释放掉
            try {
                interProcessMutexLock.release();
            } catch (Exception e) {
                e.printStackTrace();
                // 一定要发邮件

            }
        }
    }
}
